# -*- coding: utf-8 -*-

import struct

from error import ClientErr
from utils import print_err
from tcp_server import TcpServer


class ForwardServer(TcpServer):

    NONE_EID = 0
    BROADCAST_EID = -1
    MAX_DST_NUM = 1024

    CMD_HEARTBEAT = 0
    CMD_LOGIN = 1
    CMD_LOGOUT = 2
    CMD_FORWARD = 3

    # 报文格式：cmd, src, dst_num, [dst1, dst2, ..., payload]
    #          0   4   8        12     16

    def __init__(self, port, addr='0.0.0.0'):
        super(ForwardServer, self).__init__(port, addr)
        self._cmd_proc = [
            self._cmd_heartbeat,     # 0
            self._cmd_login,         # 1
            self._cmd_logout,        # 2
            self._cmd_forward,       # 3
        ]
        self._eid_conn = {}         # eid: conn

    def recv_proc(self, conn, data):
        if len(data) < 12:
            raise ClientErr("Data length too short = {}".format(data))
        cmd = struct.unpack('i', data[:4])[0]
        if 0 <= cmd < len(self._cmd_proc):
            self._cmd_proc[cmd](conn, data)
        else:
            raise ClientErr("Unknown cmd {}".format(cmd))

    def offline_proc(self, conn):     # 掉线处理：如果self._eid_conn有记录说明没有正常logout，广播logout, 删除记录
        print("A Client offline: {}".format(conn))
        eid_del = None
        for k_eid, v_conn in self._eid_conn.iteritems():
            if v_conn == conn:
                eid_del = k_eid
                break
        if eid_del is not None:
            data = ForwardServer.make_data(ForwardServer.CMD_LOGOUT, eid_del, [ForwardServer.BROADCAST_EID])
            self._forward_data(eid_del, [ForwardServer.BROADCAST_EID], data)
            del(self._eid_conn[eid_del])

    def _cmd_login(self, conn, data):            # 添加self._eid_conn中上线者的记录，再转发
        src, dst_list, next_pos = ForwardServer.parse_src_dst(data)
        print("Eid {} login".format(src))
        if len(data) < next_pos + 4:
            raise ClientErr("Client login packet error, no need sync segment")

        if len(self._eid_conn) > 0:
            need_sync_eid = min(self._eid_conn.iterkeys())
            self._eid_conn[src] = conn
            need_sync_data = data[:next_pos] + struct.pack('i', 1) + data[next_pos+4:]
            self._forward_data([], [need_sync_eid], need_sync_data)
            self._forward_data([src, need_sync_eid], dst_list, data)
        else:
            self._eid_conn[src] = conn

    def _cmd_logout(self, conn, data):           # 删除self._eid_conn中下线者的记录，再转发
        src, dst_list, next_pos = ForwardServer.parse_src_dst(data)
        print("Eid {} logout".format(src))
        if src in self._eid_conn:
            if self._eid_conn[src] != conn:
                print_err("Connection eid not match, eid = {}".format(src))
            del(self._eid_conn[src])
        else:
            print_err("Not found login eid = ".format(src))
        self._forward_data(src, dst_list, data)

    def _cmd_heartbeat(self, conn, data):
        pass

    def _cmd_forward(self, conn, data):
        src, dst_list, next_pos = ForwardServer.parse_src_dst(data)
        print("Eid {} forward to {}".format(src, dst_list))
        self._forward_data(src, dst_list, data)

    @staticmethod
    def parse_src_dst(data):       # return: src, dst_list, payload_pos
        src, dst_num = struct.unpack('2i', data[4: 12])
        if dst_num <= 0:
            dst_list = []
            next_pos = 12
        else:
            next_pos = 12 + 4 * dst_num
            if len(data) < next_pos:
                raise ClientErr("Parse src dst error: dst list error, dst_num = {}".format(dst_num))
            dst_list = list(struct.unpack(str(dst_num)+'i', data[12: next_pos]))
        return src, dst_list, next_pos

    @staticmethod
    def make_data(cmd, src, dst_list, payload=''):
        header = struct.pack(str(3+len(dst_list))+'i', cmd, src, len(dst_list), *dst_list)
        return header + payload

    # @staticmethod
    # def _parse_src_dst(data):
    #     cmd, src, dst = struct.unpack('iii', data[:12])
    #     return src, dst

    def _forward_data(self, exclude_eid_list, dst_eid_list, data):      # 不会修改源目的地址
        if isinstance(exclude_eid_list, int):
            exclude_eid_list = [exclude_eid_list]

        if len(dst_eid_list) == 0:
            pass
        elif ForwardServer.BROADCAST_EID in dst_eid_list:
            self._broadcast(exclude_eid_list, data)
        else:
            for each_dst_eid in dst_eid_list:
                if each_dst_eid == ForwardServer.NONE_EID:
                    continue
                if each_dst_eid in self._eid_conn and each_dst_eid not in exclude_eid_list:
                    ForwardServer.send(self._eid_conn[each_dst_eid], data)
                else:
                    print_err("Invalid dst eid {}, exclude eid = {}".format(each_dst_eid, exclude_eid_list))

    def _broadcast(self, exclude_eid_list, data):
        for k_eid, v_conn in self._eid_conn.iteritems():
            if k_eid not in exclude_eid_list:
                ForwardServer.send(v_conn, data)


def test1():
    print("test1 running")
    ForwardServer(55340).run()


if __name__ == '__main__':
    test1()
